// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Serialization;

using Tierless.Framework.Data;
using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.Entities
{
	/// <summary>
	/// Tracks the child entities of a given entity.
	/// </summary>
	[DataContract]
	#if !SILVERLIGHT
	public class EntityChildren : ICollection<EntitySet>, IDeserializationCallback
	#else
	public class EntityChildren : ICollection<EntitySet>
	#endif
	{
		/// <summary>
		/// Initializes a new instance of the EntityChildren class.
		/// </summary>
		/// <param name="entity">The entity for which the children will be tracked.</param>
		public EntityChildren(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException("AEntity");

			_entity = entity;
		}

		[DataMember]
		private Entity _entity;

		/// <summary>
		/// Gets the entity containing the children.
		/// </summary>
		public Entity Entity
		{
			get { return _entity; }
		}

		[DataMember]
		private Dictionary<string, EntitySet> _children = new Dictionary<string, EntitySet>();

		/// <summary>
		/// Returns the entity set containing the child entities for a specific child reference.
		/// </summary>
		/// <param name="childName">The name of the child reference.</param>
		/// <returns>The entity set containing the child entities for the reference.</returns>
		public EntitySet this[string childName]
		{
			get
			{
				EntitySet result;
				if (!_children.TryGetValue(childName, out result))
				{
					var reference = _entity.EntityType.References.GetChildReference(childName);
					result = new EntitySet(reference.SourceEntityTypeName);
					result.CollectionChanged += SetChildSourceColumn;
					_children.Add(childName, result);
				}
				return result;
			}
		}

		internal Dictionary<string, EntitySet> Entries
		{
			get { return _children; }
		}

		private void SetChildSourceColumn(object sender, NotifyCollectionChangedEventArgs args)
		{
			if ((args.Action == NotifyCollectionChangedAction.Add) || (args.Action == NotifyCollectionChangedAction.Replace))
			{
				foreach (Entity child in args.NewItems)
				{
					var reference = _entity.EntityType.References.FindChildReferenceBySource(child.EntityTypeName);
					if (reference != null)
					{
						for (var index = 0; index < reference.SourceColumns.Count; index++)
						{
							var sourceColumn = child[reference.SourceColumns[index].Name];
							var targetColumn = _entity[reference.TargetColumns[index].Name];
							if (sourceColumn != targetColumn)
								sourceColumn.Value = targetColumn.Value;
						}
					}
				}
			}
		}

		public void Add(EntitySet item)
		{
			throw new InvalidOperationException("Cannot Add, EntityChildren is a read-only collection.");
		}

		public bool Remove(EntitySet item)
		{
			throw new InvalidOperationException("Cannot Remove, EntityChildren is a read-only collection.");
		}

		public void Clear()
		{
			throw new InvalidOperationException("Cannot Clear, EntityChildren is a read-only collection.");
		}

		/// <summary>
		/// Returns true if the collection contains the given entity set.
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		public bool Contains(EntitySet item)
		{
			return _children.ContainsValue(item);
		}

		/// <summary>
		/// Copies the contents of the collection to an array of entity sets.
		/// </summary>
		/// <param name="array"></param>
		/// <param name="arrayIndex"></param>
		public void CopyTo(EntitySet[] array, int arrayIndex)
		{
			var values = _children.Values.ToArray();
			for (var index = arrayIndex; index < Math.Min(array.Length, Count + arrayIndex); index++)
				array[index] = values[index + arrayIndex];
		}

		/// <summary>
		/// Returns the number of child entity sets in the collection.
		/// </summary>
		public int Count
		{
			get { return _children.Count; }
		}

		public bool IsReadOnly
		{
			get { return true; }
		}

		/// <summary>
		/// Returns an enumerator for iterating over the child entity sets in the collection.
		/// </summary>
		/// <returns>An enumerator for iterating over the child entity sets in the collection.</returns>
		public IEnumerator<EntitySet> GetEnumerator()
		{
			foreach (var reference in _entity.EntityType.References)
			{
				EntitySet child;
				if ((reference.Kind == ReferenceKind.Child) && _children.TryGetValue(reference.IndexName, out child))
					yield return child;
			}
		}

		/// <summary>
		/// Returns an enumerator for iterating over the child entity sets in the collection.
		/// </summary>
		/// <returns>An enumerator for iterating over the child entity sets in the collection.</returns>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
		
		#if !SILVERLIGHT

		#region IDeserializationCallback Members

		public void OnDeserialization(object sender)
		{
			foreach (var entitySet in _children.Values)
			{
				entitySet.CollectionChanged += SetChildSourceColumn;
			}
		}

		#endregion
		
		#endif
	}
}